package org.wat.wcy.isi.mmazur.bp.io.xpdl.generator;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.wat.wcy.isi.mmazur.bp.io.model.Coordinate;
import org.wat.wcy.isi.mmazur.bp.io.model.Node;
import org.wat.wcy.isi.mmazur.bp.io.model.Transition;
import org.wat.wcy.isi.mmazur.bp.io.model.enums.NodeType;
import org.wat.wcy.isi.mmazur.bp.io.model.enums.TransitionType;
import org.wat.wcy.isi.mmazur.bp.io.model.transport.BusinessProcessMatrix;

public class XPDLGenerator implements IXPDLGenerator {

	private IdGenerator idGen;
	private Map<String, String> processMap; // nodeId -> generated process id
	private Map<String, String> processNameMap; // generated process id -> node Name
	private Map<String, List<String>> poolLanes; // generated process id -> node Name

	@Override
	public String generateXml(final String processName,
			BusinessProcessMatrix matrix) {
		if (matrix == null) {
			return null;
		}
		idGen = new IdGenerator();
		processMap = new HashMap<String, String> ();
		processNameMap = new HashMap<String, String> ();
		poolLanes = new HashMap<String, List<String>> ();
		String xpdlContent = getFileContent("/xpdl.xml");
		String content = xpdlContent;
		Map<String, String> map = new HashMap<String, String>();
		map.put("%PACKAGE_HEADER%", createProcessHeader(processName));
		map.put("%POOLS%", createPools(processName, matrix));
		
		StringBuilder strbMessages = new StringBuilder();
		StringBuilder strbAssociations = new StringBuilder();
		
		Transition[][] arr = matrix.getNodesMatrix();
		int n = arr.length;
		for (int j = 0; j < n; j++) {
			Node target = matrix.getNodesList().get(j);
			for (int i = 0; i < n; i++) {
				Node source = matrix.getNodesList().get(i);
				
				Transition transition = arr[i][j];
				if (transition.getType() == TransitionType.MESSAGE) {
					strbMessages.append(createMessageFlow(transition, source, target));
				} else if (transition.getType() == TransitionType.ASSOCIATION) {
					strbAssociations.append(createAssociation(transition, source, target));
				}
			}
		}
		map.put("%MESSAGE_FLOWS%", strbMessages.toString());
		map.put("%ASSOCIATIONS%", strbAssociations.toString());
		
		StringBuilder strbArtifacts = new StringBuilder();
		
		for (Node node : matrix.getNodesList()) {
			NodeType t = node.getType();
			if (t == NodeType.GROUP || t == NodeType.DATAOBJECT || t == NodeType.ANNOTATION) {
				strbArtifacts.append(createArtifact(node));
			}
		}
		StringBuilder strbProcesses = new StringBuilder();
		
		for (String poolId : processMap.keySet()) {
			String processId = processMap.get(poolId);
			strbProcesses.append(createProcess(poolId, processId, matrix));
		}
		
		map.put("%ARTIFACTS%", strbArtifacts.toString());
		map.put("%WORKFLOW_PROCESSES%", strbProcesses.toString());
		
		for (String key : map.keySet()) {
			content = content.replaceFirst(key, map.get(key));
		}
		return content;
	}



	private Object createProcess(String poolId, String processId, BusinessProcessMatrix matrix) {
		Map<String, String> map = new HashMap<String, String>();
		map.put("%ID%", processId);
		map.put("%NAME%", processNameMap.get(processId));
		
		List<Node> processNodes = new ArrayList<Node> ();
		for (Node node : matrix.getNodesList()) {
			if (node.getLane().getId().toString().equals(poolId) && node.getType() != NodeType.POOL) {
				processNodes.add(node);
				
			} else if (poolLanes.containsKey(poolId)){
				for (String laneId : poolLanes.get(poolId)) {
					if (StringUtils.equalsIgnoreCase(laneId, node.getLane().getId().toString())) {
						processNodes.add(node);
					}
				}
			}
		}
		
		StringBuilder strbActivities = new StringBuilder();
		for (Node node : processNodes) {
			strbActivities.append(createActivity(node));
		}
		
		StringBuilder strbTransitions = new StringBuilder();
		
		Transition[][] arr = matrix.getNodesMatrix();
		int n = arr.length;
		for (int j = 0; j < n; j++) {
			Node target = matrix.getNodesList().get(j);
			for (int i = 0; i < n; i++) {
				Transition transition = arr[i][j];
				if (transition.getType() != TransitionType.SEQUENCE) {
					continue;
				}
				Node source = matrix.getNodesList().get(i);
				for (Node activityNode : processNodes) {
					if (source.getId().toString().equals(activityNode.getId().toString())) {
						strbTransitions.append(createTransition(transition, source, target));
					}
				}
				
			}
		}
		
		StringBuilder strbActivitySets = new StringBuilder();
		
		map.put("%ACTIVITY_SETS%", strbActivitySets.toString());
		map.put("%ACTIVITIES%", strbActivities.toString());
		map.put("%TRANSITIONS%", strbTransitions.toString());
		
		return prepareElement("/workflow_processes.xml", map);
	}



	private Object createTransition(Transition t, Node source,
			Node target) {
		Map<String, String> map = new HashMap<String, String>();
		map.put("%ID%", t.getId().toString());
		map.put("%NAME%", t.getName());
		map.put("%FROM%", source.getId().toString());
		map.put("%TO%", target.getId().toString());
		StringBuilder strbCoords = new StringBuilder();
		for (Coordinate coord : t.getCoordinates()) {
			if (coord.isNotNull()) {
				strbCoords.append(createCoordinate(coord));
			}
		}
		map.put("%COORDINATES%", strbCoords.toString());
		return prepareElement("/transition.xml", map);
	}



	private String createActivity(Node node) {
		final Map<String, String> map = new HashMap<String, String>();
		map.put("%ID%", node.getId().toString());
		map.put("%NAME%", node.getName());
		map.put("%X%", Integer.toString(node.getGraphicsInfo().getX()));
		map.put("%Y%", Integer.toString(node.getGraphicsInfo().getY()));
		map.put("%WIDTH%", Integer.toString(node.getGraphicsInfo().getWidth()));
		map.put("%HEIGHT%", Integer.toString(node.getGraphicsInfo().getHeight()));
		String implementaton = "";
		String gatewaytype = "";
		String event = "";
		if (node.getType().name().startsWith("TASK")) {
			implementaton = String.format("<Implementation><%s/></Implementation>",node.getType().getXPDLName());
		} else if (node.getType().name().startsWith("GATEWAY")) {
			gatewaytype = String.format("Gateway=\"%s\"",node.getType().getXPDLName());
		} else if (node.getType().name().startsWith("START")) {
			if (node.getType() == NodeType.START_NONE) {
				event = "<StartEvent Trigger=\"None\" />"; 
			} else {
				String type = node.getType().getXPDLName();
				event = String.format("<StartEvent Trigger=\"%s\"><TriggerResult%s/></StartEvent>", type, type); 
			}
		} else if (node.getType().name().startsWith("END")) {
			if (node.getType() == NodeType.END_NONE) {
				event = "<EndEvent/>"; 
			} else {
				String type = node.getType().getXPDLName();
				event = String.format("<EndEvent Result=\"%s\"><TriggerResult%s/></EndEvent>", type, type); 
			}
		} else if (node.getType().name().startsWith("INTERMEDIATE")) {
		}
		map.put("%IMPLEMENTATION%", implementaton);
		map.put("%GATEWAYTYPE%", gatewaytype);
		map.put("%EVENT%", event);
		map.put("%LOOP%", createLoop(node));
		return prepareElement("/activity.xml", map);
	}



	private String createPools(String processName, BusinessProcessMatrix matrix) {
		List<Node> nodesList = matrix.getNodesList();
		StringBuilder strb = new StringBuilder();
		for (Node node : nodesList) {
			if (node.getType() == NodeType.POOL) {
				String processId = idGen.next();
				String lanes = "";
				StringBuilder strbLanes = new StringBuilder();
				for (Node lane : nodesList) {
					if (lane.getType() == NodeType.LANE
							&& lane.getParent().getId().equals(node.getId())) {
						final String laneStr = createLane(lane.getId().toString(), lane.getName(), 
								node.getId().toString(), 
								lane.getGraphicsInfo().getX(), 
								lane.getGraphicsInfo().getY(), 
								lane.getGraphicsInfo().getWidth(),
								lane.getGraphicsInfo().getHeight());
						strbLanes.append(laneStr);
						strbLanes.append("\n");
						String key = node.getId().toString();
						if (!poolLanes.containsKey(key)) {
							poolLanes.put(key, new ArrayList<String> ());
						}
						poolLanes.get(key).add(lane.getId().toString());
					}
				}
				lanes = strbLanes.toString();
				
				processMap.put(node.getId().toString(), processId);
				processNameMap.put(processId, node.getName());
				
				String pool = createPool(node.getId().toString(), processId,
						processName, node.getGraphicsInfo().getX(), node
								.getGraphicsInfo().getY(), node
								.getGraphicsInfo().getWidth(), node
								.getGraphicsInfo().getHeight(), lanes);
				strb.append(pool);
				strb.append("\n");
			}
		}
		return strb.toString();
	}

	private String getFileContent(String fileName) {
		InputStream input = null;
		try {
			input = this.getClass().getResourceAsStream(fileName);
			return IOUtils.toString(input);
		} catch (Throwable e) {
			e.printStackTrace();
		} finally {
			IOUtils.closeQuietly(input);
		}
		return null;
	}

	private String prepareElement(String fileName, Map<String, String> map) {
		String content = getFileContent(fileName);
		for (String key : map.keySet()) {
			content = content.replaceFirst(key, map.get(key));
		}
		return content;
	}

	private String createPool(String id, String processId, String processName,
			int x, int y, int width, int height, String lanes) {
		Map<String, String> map = new HashMap<String, String>();
		map.put("%PROCESS_ID%", processId);
		map.put("%ID%", id);
		map.put("%PROCESS_NAME%", processName);
		map.put("%X%", Integer.toString(x));
		map.put("%Y%", Integer.toString(y));
		map.put("%WIDTH%", Integer.toString(width));
		map.put("%HEIGHT%", Integer.toString(height));
		map.put("%LANES%", lanes);
		return prepareElement("/pool.xml", map);
	}
	
	private Object createArtifact(Node node) {
		Map<String, String> map = new HashMap<String, String>();
		map.put("%ID%", node.getId().toString());
		map.put("%ARTIFACT_TYPE%", node.getType().getXPDLName());
		map.put("%GROUP%", "");
		map.put("%X%", Integer.toString(node.getGraphicsInfo().getX()));
		map.put("%Y%", Integer.toString(node.getGraphicsInfo().getY()));
		map.put("%WIDTH%", Integer.toString(node.getGraphicsInfo().getWidth()));
		map.put("%HEIGHT%", Integer.toString(node.getGraphicsInfo().getHeight()));
		return prepareElement("/artifact.xml", map);
	}

	private String createLane(String id, String name, String parendPool, int x,
			int y, int width, int height) {
		Map<String, String> map = new HashMap<String, String>();
		map.put("%ID%", id);
		map.put("%PARENT_POOL%", parendPool);
		map.put("%X%", Integer.toString(x));
		map.put("%Y%", Integer.toString(y));
		map.put("%WIDTH%", Integer.toString(width));
		map.put("%HEIGHT%", Integer.toString(height));
		return prepareElement("/lane.xml", map);
	}

	private String createCoordinate(int x, int y) {
		Map<String, String> map = new HashMap<String, String>();
		map.put("%X%", Integer.toString(x));
		map.put("%Y%", Integer.toString(y));
		return prepareElement("/coordinate.xml", map);
	}
	
	private String createMessageFlow(Transition t, Node source, Node target) {
		Map<String, String> map = new HashMap<String, String>();
		map.put("%ID%", t.getId().toString());
		map.put("%NAME%", t.getName());
		map.put("%SOURCE_ID%", source.getId().toString());
		map.put("%TARGET_ID%", target.getId().toString());
		StringBuilder strbCoords = new StringBuilder();
		for (Coordinate coord : t.getCoordinates()) {
			if (coord.isNotNull()) {
				strbCoords.append(createCoordinate(coord));
			}
		}
		map.put("%COORDINATES%", strbCoords.toString());
		return prepareElement("/message_flow.xml", map);
	}
	
	private Object createAssociation(Transition t, Node source,
			Node target) {
		Map<String, String> map = new HashMap<String, String>();
		map.put("%ID%", t.getId().toString());
		map.put("%NAME%", t.getName());
		map.put("%SOURCE_ID%", source.getId().toString());
		map.put("%TARGET_ID%", target.getId().toString());
		StringBuilder strbCoords = new StringBuilder();
		for (Coordinate coord : t.getCoordinates()) {
			if (coord.isNotNull()) {
				strbCoords.append(createCoordinate(coord));
			}
		}
		map.put("%COORDINATES%", strbCoords.toString());
		return prepareElement("/association.xml", map);
	}

	private Object createCoordinate(Coordinate coord) {
		if (coord == null) {
			return "";
		}
		return createCoordinate(coord.getX(), coord.getY());
	}


	private String createProcessHeader(String name) {
		Map<String, String> map = new HashMap<String, String>();
		map.put("%NAME%", name);
		return prepareElement("/package_header.xml", map);
	}
	
	private String createLoop(Node node) {
		Map<String, String> map = new HashMap<String, String>();
		switch (node.getLoop().getType()) {
		case NONE:
			return "";
		case STANDARD:
			map.put("%TESTTIME%", node.getLoop().getTestTime().getName());
			break;
		case MULTIINSTANCE:
			map.put("%ORDERING%", node.getLoop().getMiLoopOrdering().getName());
			break;
		}
		map.put("%TYPE%", node.getLoop().getType().getXpdlName());
		map.put("%CONDITION%", node.getLoop().getCondition());
		return prepareElement("/package_header.xml", map);
	}
}
